/* -LICENSE-START-
** Copyright (c) 2009 Blackmagic Design
**
** Permission is hereby granted, free of charge, to any person or organization
** obtaining a copy of the software and accompanying documentation covered by
** this license (the "Software") to use, reproduce, display, distribute,
** execute, and transmit the Software, and to prepare derivative works of the
** Software, and to permit third-parties to whom the Software is furnished to
** do so, all subject to the following:
** 
** The copyright notices in the Software and this entire statement, including
** the above license grant, this restriction and the following disclaimer,
** must be included in all copies of the Software, in whole or in part, and
** all derivative works of the Software, unless such copies or derivative
** works are solely in the form of machine-executable object code generated by
** a source language processor.
** 
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
** FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
** SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
** FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
** ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
** DEALINGS IN THE SOFTWARE.
** -LICENSE-END-
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include <fcntl.h>

#include "DeckLinkAPI.h"

#include "DeckLinkCapture.h"

#include <opencv/cv.h>
#include <opencv/highgui.h>

pthread_mutex_t					sleepMutex;
pthread_cond_t					sleepCond;

IDeckLink 						*deckLink;
IDeckLinkInput					*deckLinkInput;
IDeckLinkDisplayModeIterator	*displayModeIterator;

static BMDTimecodeFormat		g_timecodeFormat = 0;
static int                      g_videoModeIndex = 14;

static int						g_audioChannels = 2;
static int						g_audioSampleDepth = 16;

static int						g_maxFrames = -1;

static unsigned long 			frameCount = 0;

DeckLinkCapture::DeckLinkCapture(int width, int height) : m_refCount(0)
{
	pthread_mutex_init(&m_mutex, NULL);

    m_Width = width;
    m_Height = height;
}

DeckLinkCapture::~DeckLinkCapture()
{
	pthread_mutex_destroy(&m_mutex);

    bail();
}

ULONG DeckLinkCapture::AddRef(void)
{
	pthread_mutex_lock(&m_mutex);
		m_refCount++;
	pthread_mutex_unlock(&m_mutex);

	return (ULONG)m_refCount;
}

ULONG DeckLinkCapture::Release(void)
{
	pthread_mutex_lock(&m_mutex);
		m_refCount--;
	pthread_mutex_unlock(&m_mutex);

	if (m_refCount == 0)
	{
		delete this;
		return 0;
	}

	return (ULONG)m_refCount;
}

HRESULT DeckLinkCapture::VideoInputFrameArrived(IDeckLinkVideoInputFrame* videoFrame, IDeckLinkAudioInputPacket* audioFrame)
{
	IDeckLinkVideoFrame*	                rightEyeFrame = NULL;
	IDeckLinkVideoFrame3DExtensions*        threeDExtensions = NULL;
	void*					frameBytes;
	void*					audioFrameBytes;
	
	// Handle Video Frame
	if(videoFrame)
	{	
		// If 3D mode is enabled we retreive the 3D extensions interface which gives.
		// us access to the right eye frame by calling GetFrameForRightEye() .
		if ( (videoFrame->QueryInterface(IID_IDeckLinkVideoFrame3DExtensions, (void **) &threeDExtensions) != S_OK) ||
			(threeDExtensions->GetFrameForRightEye(&rightEyeFrame) != S_OK))
		{
			rightEyeFrame = NULL;
		}
		
		if (threeDExtensions)
			threeDExtensions->Release();

		if (videoFrame->GetFlags() & bmdFrameHasNoInputSource)
		{
			fprintf(stderr, "Frame received (#%lu) - No input signal detected\n", frameCount);
		}
		else
		{
			const char *timecodeString = NULL;
			if (g_timecodeFormat != 0)
			{
				IDeckLinkTimecode *timecode;
				if (videoFrame->GetTimecode(g_timecodeFormat, &timecode) == S_OK)
				{
                    // Ilias Commented.
                //	timecode->GetString(&timecodeString);
				}
			}

			fprintf(stderr, "Frame received (#%lu) [%s] - %s - Size: %li bytes\n", 
				frameCount,
				timecodeString != NULL ? timecodeString : "No timecode",
				rightEyeFrame != NULL ? "Valid Frame (3D left/right)" : "Valid Frame", 
				videoFrame->GetRowBytes() * videoFrame->GetHeight());

			if (timecodeString)
				free((void*)timecodeString);
			
				videoFrame->GetBytes(&frameBytes);

				if (rightEyeFrame)
				{
					rightEyeFrame->GetBytes(&frameBytes);
                }
		}
		
		if (rightEyeFrame)
			rightEyeFrame->Release();

		frameCount++;

		if (g_maxFrames > 0 && frameCount >= g_maxFrames)
		{
			pthread_cond_signal(&sleepCond);
		}
	}

	// Handle Audio Frame
	if (audioFrame)
	{
        audioFrame->GetBytes(&audioFrameBytes);
	}
    return S_OK;
}

HRESULT DeckLinkCapture::VideoInputFormatChanged(BMDVideoInputFormatChangedEvents events, IDeckLinkDisplayMode *mode, BMDDetectedVideoInputFormatFlags)
{
    return S_OK;
}

QString DeckLinkCapture::toQString(CFStringRef str) {
    if (!str)
        return QString();

    CFIndex length = CFStringGetLength(str);
    if (length == 0)
        return QString();

    QString string(length, Qt::Uninitialized);
    CFStringGetCharacters(str, CFRangeMake(0, length), reinterpret_cast<UniChar *>
        (const_cast<QChar *>(string.unicode())));
    return string;
}

void DeckLinkCapture::convertFrameToOpenCV(void* frameBytes, IplImage * m_RGB, int width, int height) {
    if(!m_RGB)
        m_RGB = cvCreateImage(cvSize(width, height), IPL_DEPTH_8U, 3);

    unsigned char* pData = (unsigned char *) frameBytes;

    for(int i = 0, j=0; i < width * height * 3; i+=6, j+=4) {
        unsigned char u = pData[j];
        unsigned char y = pData[j+1];
        unsigned char v = pData[j+2];

        //fprintf(stderr, "%d\n", v);
        m_RGB->imageData[i+2] = 1.0*y + 8 + 1.402*(v-128);               // r
        m_RGB->imageData[i+1] = 1.0*y - 0.34413*(u-128) - 0.71414*(v-128);   // g
        m_RGB->imageData[i] = 1.0*y + 1.772*(u-128) + 0;                            // b

        y = pData[j+3];
        m_RGB->imageData[i+5] = 1.0*y + 8 + 1.402*(v-128);               // r
        m_RGB->imageData[i+4] = 1.0*y - 0.34413*(u-128) - 0.71414*(v-128);   // g
        m_RGB->imageData[i+3] = 1.0*y + 1.772*(u-128) + 0;
    }
}

IplImage* DeckLinkCapture::captureLastFrame() {
    pthread_mutex_lock(&sleepMutex);
    pthread_cond_wait(&sleepCond, &sleepMutex);
    pthread_mutex_unlock(&sleepMutex);

    if(!lastImage)
        lastImage = cvCreateImage(cvSize(m_Width, m_Height), IPL_DEPTH_8U, 3);

    convertFrameToOpenCV(frameBytes, lastImage, m_Width, m_Height);

    return lastImage;
}

int DeckLinkCapture::start() {
    // Adapted from Decklink linux example:

    deckLinkIterator = CreateDeckLinkIteratorInstance();
    DeckLinkCapture             *delegate;
    IDeckLinkDisplayMode		*displayMode;
    BMDVideoInputFlags			inputFlags = 0;
    BMDDisplayMode				selectedDisplayMode = bmdModeHD720p5994;
    BMDPixelFormat				pixelFormat = bmdFormat8BitYUV;
    int							displayModeCount = 0;
    int							exitStatus = 1;
    int							ch;
    bool 						foundDisplayMode = false;
    HRESULT						result;

    pthread_mutex_init(&sleepMutex, NULL);
    pthread_cond_init(&sleepCond, NULL);

    if (!deckLinkIterator)
    {
        fprintf(stderr, "This application requires the DeckLink drivers installed.\n");
        bail();
    }

    /* Connect to the first DeckLink instance */
    result = deckLinkIterator->Next(&deckLink);
    if (result != S_OK)
    {
        fprintf(stderr, "No DeckLink PCI cards found.\n");
        bail();
    }

    if (deckLink->QueryInterface(IID_IDeckLinkInput, (void**)&deckLinkInput) != S_OK)
        bail();

    delegate = new DeckLinkCapture(1280, 720);
    deckLinkInput->SetCallback(delegate);

    // Obtain an IDeckLinkDisplayModeIterator to enumerate the display modes supported on output
    result = deckLinkInput->GetDisplayModeIterator(&displayModeIterator);
    if (result != S_OK)
    {
        fprintf(stderr, "Could not obtain the video output display mode iterator - result = %08x\n", result);
        bail();
    }

    // (...) command line options...

    if (g_videoModeIndex < 0)
    {
        fprintf(stderr, "No video mode specified\n");
    }

    while (displayModeIterator->Next(&displayMode) == S_OK)
    {
        if (g_videoModeIndex == displayModeCount)
        {
            BMDDisplayModeSupport result;

            CFStringRef displayModeName;

            foundDisplayMode = true;
            displayMode->GetName(&displayModeName);

            QString     dmName      = DeckLinkCapture::toQString(displayModeName);
            QByteArray  byteArray   = dmName.toUtf8();
            const char* cString     = byteArray.constData();
            //QString qString2 = QString::fromUtf8(cString);
            // example of converting back to QString.
            fprintf(stderr, "Display mode name: %s \n", cString);

            selectedDisplayMode = displayMode->GetDisplayMode();

            deckLinkInput->DoesSupportVideoMode(selectedDisplayMode, pixelFormat, bmdVideoInputFlagDefault, &result, NULL);

            if (result == bmdDisplayModeNotSupported)
            {
                fprintf(stderr, "The display mode %s is not supported with the selected pixel format\n", cString);
                bail();
            }

            if (inputFlags & bmdVideoInputDualStream3D)
            {
                if (!(displayMode->GetFlags() & bmdDisplayModeSupports3D))
                {
                    fprintf(stderr, "The display mode %s is not supported with 3D\n", cString);
                    bail();
                }
            }

            break;
        }
        displayModeCount++;
        displayMode->Release();
    }

    if (!foundDisplayMode)
    {
        fprintf(stderr, "Invalid mode %d specified\n", g_videoModeIndex);
        bail();
    }

    result = deckLinkInput->EnableVideoInput(selectedDisplayMode, pixelFormat, inputFlags);
    if(result != S_OK)
    {
        fprintf(stderr, "Failed to enable video input. Is another application using the card?\n");
        bail();
    }

    result = deckLinkInput->EnableAudioInput(bmdAudioSampleRate48kHz, g_audioSampleDepth, g_audioChannels);
    if(result != S_OK)
    {
        bail();
    }

    result = deckLinkInput->StartStreams();
    if(result != S_OK)
    {
        bail();
    }

    // All Okay.
    exitStatus = 0;

    // Block main thread until signal occurs
//    pthread_mutex_lock(&sleepMutex);
//    pthread_cond_wait(&sleepCond, &sleepMutex);
//    pthread_mutex_unlock(&sleepMutex);
//    fprintf(stderr, "Stopping Capture\n");

    // -- done adapting from Decklink linux example.
}

void DeckLinkCapture::bail() {
   //m_Camera1->stopCapture();

//    if (this->m_DeckLink != NULL) {
//        this->m_DeckLink->Release();
//        this->m_DeckLink = NULL;
//    }

//    if (this->m_SelectedDecklinkDevice != NULL) {
//        this->m_SelectedDecklinkDevice->Release();
//        this->m_SelectedDecklinkDevice = NULL;
//    }

//bail:

    if (displayModeIterator != NULL)
    {
        displayModeIterator->Release();
        displayModeIterator = NULL;
    }

    if (deckLinkInput != NULL)
    {
        deckLinkInput->Release();
        deckLinkInput = NULL;
    }

    if (deckLink != NULL)
    {
        deckLink->Release();
        deckLink = NULL;
    }

    if (deckLinkIterator != NULL)
        deckLinkIterator->Release();
}
